
#include "user_password.h"

#if GKT_USER_PASSWORD_DYNAMIC_ENABLE

static uint8_t user_dynpwd_get_check_value(const uint8_t *units, 
				uint32_t units_size)
{
	uint32_t check_value, i;

	check_value = 0;
	for (i = 0; i < units_size; i++)
		check_value += units[i] ^ (i + 1);
	while (check_value > 0x0f)
		check_value = (check_value >> 4) ^ (check_value & 0x0f);

	return (uint8_t)check_value;
}

static void user_dynpwd_rotate_right_shift(uint8_t *units, 
				uint32_t units_size, uint32_t shift)
{
	uint32_t shift_units_nums, shift_bits;
	uint32_t i, offset, mask;
	uint8_t tmp[4];

	if (!shift)
		shift = 0x10;

	shift_units_nums = (shift >> 2) % units_size;
	if (shift_units_nums) {
		offset = units_size - shift_units_nums;
		for (i = 0; i < shift_units_nums; i++)
			tmp[i] = units[offset + i];
		while (offset-- > 0)
			units[offset + shift_units_nums] = units[offset];
		while (shift_units_nums--)
			units[shift_units_nums] = tmp[shift_units_nums];
	}

	shift_bits = shift & 3;
	if (shift_bits) {
		mask = (1U << shift_bits) - 1;
		tmp[0] = (units[units_size - 1] & mask) << (4 - shift_bits);
		for (i = units_size - 1; i > 0; i--) {
			units[i] >>= shift_bits;
			units[i] |= (units[i - 1] & mask) << (4 - shift_bits);
		}
		units[0] = tmp[0] | (units[0] >> shift_bits);
	}
}

static void user_dynpwd_xor(uint8_t *data_units, 
				uint32_t data_units_size, uint8_t xor_value)
{
	uint32_t i;

	if (xor_value != 0) {
		for (i = 0; i < data_units_size; i++)
			data_units[i] ^= xor_value;
	}
	else {
		for (i = 0; i < data_units_size; i++)
			data_units[i] ^= (uint8_t)(i & 0x0f);
	}
}

static uint32_t user_dynpwd_build_key_units(uint8_t *key_units, 
			const char *key_string, uint32_t key_length)
{
	uint64_t key_value;
	uint32_t key_units_size, i;
	uint8_t ch_value, ch_value_prev;
	uint8_t arith_seq, delta_base, delta;
	char key_ch;

	i = 0;
	key_value = key_length;
	arith_seq = 1;
	delta_base = 0;
	ch_value_prev = 0;
	while ((i < key_length) 
		&& ((key_ch = key_string[i]) != '\0')) 
	{
		ch_value = key_ch - '0';
		key_value = (key_value * 10) + ch_value;

		if (arith_seq) {
			if (i > 2) {
				delta = ch_value - ch_value_prev;
				if (delta != delta_base)
					arith_seq = 0;
			}
			else if (1 == i)
				delta_base = ch_value - ch_value_prev;
			ch_value_prev = ch_value;
		}

		i++;
	}

	key_units_size = 0;
	do {
		key_units[key_units_size++] = (uint8_t)(key_value & 0x0f);
		key_value >>= 4;
	} while (key_value && (key_units_size < USER_DYNPWD_KEY_UNITS_MAXSIZE));

	if (arith_seq) {
		key_units[key_units_size++] = key_string[0] - '0' + 3;
		if (delta_base < 10)
			key_units[key_units_size++] = delta_base;
		else
			key_units[key_units_size++] = (0 - delta_base) & 0x0f;
	}

	return key_units_size;
}

static uint32_t user_dynpwd_decrypt(uint8_t *data_units, 
		uint32_t data_units_size, const char *key_string, 
		uint32_t key_length)
{
	uint8_t key_units[USER_DYNPWD_KEY_UNITS_MAXSIZE];
	uint32_t key_units_size, _data_units_size, i;
	uint8_t check_value;

	key_units_size = user_dynpwd_build_key_units(key_units,
			key_string, key_length);

	check_value = user_dynpwd_get_check_value(key_units, 
			key_units_size);
	_data_units_size = data_units_size - 1;
	if (data_units[_data_units_size] != check_value) {
		gkt_trace("user_dynpwd: decrypt check_value unmatch - (%x)(%x)\n",
			check_value, data_units[_data_units_size]);
		return 0;
	}

	for (i = key_units_size; i-- > 0; ) {
		user_dynpwd_rotate_right_shift(data_units, 
			_data_units_size, key_units[i]);
		user_dynpwd_xor(data_units, _data_units_size, 
			key_units[i]);
	}

	return _data_units_size;
}

static uint32_t user_dynpwd_unconfuse(uint8_t *data_units,
		uint32_t data_units_size)
{
	uint8_t check_value, check_value_calc;

	check_value = data_units[data_units_size - 1];
	user_dynpwd_rotate_right_shift(data_units, 
			data_units_size - 1, check_value);
	user_dynpwd_xor(data_units, data_units_size - 1, 
			check_value);

	check_value_calc = user_dynpwd_get_check_value(data_units, 
			data_units_size - 1);
	if (check_value != check_value_calc) {
		gkt_trace("user_dynpwd: unconfuse failed - %x @ %x\n",
				check_value, check_value_calc);
		return 0;
	}

	return (data_units_size - 1);
}

static uint32_t user_dynpwd_digits_2_units(const char *digits, 
					uint32_t digits_len, uint8_t *data_units)
{
	uint32_t data_units_size, digits_offset, digits_len_left;
	uint32_t value_0, value_1, value_2;

	digits_offset = 0;
	data_units_size = 0;
	while ((digits_offset + 5) <= digits_len) {
		value_1 = ((uint8_t)digits[digits_offset] - '0');
		value_0 = (((uint8_t)digits[digits_offset + 1] - '0') * 10) 
					+ ((uint8_t)digits[digits_offset + 2] - '0');
		value_2 = (((uint8_t)digits[digits_offset + 3] - '0') * 10) 
					+ ((uint8_t)digits[digits_offset + 4] - '0');

		if (!digits_offset && (9 == value_1))
			value_1 = 0;
		value_0 += (value_1 / 3) * 100;
		value_2 += (value_1 % 3) * 100;

		data_units[data_units_size++] = value_0 >> 4;
		data_units[data_units_size++] = value_0 & 0x0f;
		data_units[data_units_size++] = value_2 >> 4;
		data_units[data_units_size++] = value_2 & 0x0f;

		digits_offset += 5;
	}

	digits_len_left = digits_len - digits_offset;
	if (4 == digits_len_left) {
		value_0 = ((uint32_t)(digits[digits_offset] - '0') * 10)
					+ ((uint32_t)(digits[digits_offset + 1] - '0') * 1000)
					+ ((uint32_t)(digits[digits_offset + 2] - '0') * 100)
					+ ((uint32_t)(digits[digits_offset + 3] - '0'));
		data_units[data_units_size++] = (value_0 >> 8) & 0x0f;
		data_units[data_units_size++] = (value_0 >> 4) & 0x0f;
		data_units[data_units_size++] = value_0 & 0x0f;
	}
	else if (3 == digits_len_left) {
		value_0 = ((uint32_t)(digits[digits_offset] - '0') * 10)
					+ ((uint32_t)(digits[digits_offset + 1] - '0') * 100)
					+ ((uint32_t)(digits[digits_offset + 2] - '0'));
		data_units[data_units_size++] = (value_0 >> 4) & 0x0f;
		data_units[data_units_size++] = value_0 & 0x0f;
	}

	else if (2 == digits_len_left) {
		value_0 = ((uint32_t)(digits[digits_offset] - '0') * 10)
					+ ((uint32_t)(digits[digits_offset + 1] - '0'));
		data_units[data_units_size++] = value_0 & 0x0f;
	}
	else {
		gkt_error("user_dynpwd_d2u: digits_len(%u) invalid\n", digits_len);
		data_units_size = 0;
	}

	return data_units_size;
}

int user_dynpwd_parse_digits(user_dynpwd_parse_data_s *parse_data,
				const char *digits, uint32_t digits_size)
{
	const user_password_item_s *c_item;
	uint8_t data_units[USER_DYNPWD_DATA_UNITS_MAXSIZE];
	uint32_t data_units_size;
	uint32_t admin_nums, admin_item_index;
	uint32_t i, dw_index, offset;

	admin_nums = g_user_password_info.auth_user_used_nums[GKT_USER_AUTH_ADMIN];
	gkt_trace("user_dynpwd: parse - admin_nums = %u\n", admin_nums);
	for (i = 0; i < admin_nums; i++) {
		/* digits to units */
		data_units_size = user_dynpwd_digits_2_units(digits, 
				digits_size, data_units);

		/* decrypt */
		admin_item_index = g_user_dynpwd_info.admin_item_indexs[i];
		GKT_ASSERT(admin_item_index < g_user_password.instance.item_available_nums);
		c_item = USER_PASSWORD_ITEM_C(admin_item_index);
		gkt_trace("user_dynpwd: (%u) user_index(%u), authority(%u)\n", i, 
			c_item->header.user_index, c_item->header.authority);

		data_units_size = user_dynpwd_decrypt(data_units,
				data_units_size, (const char *)c_item->data,
				c_item->data_length);
		if (!data_units_size)
			continue;

		/* unconfuse */
		data_units_size = user_dynpwd_unconfuse(data_units, 
				data_units_size);
		if (data_units_size > 0)
			break;		
	}

	if (i >= admin_nums)
		return GKT_ENOTEXIST;

	parse_data->user_index = c_item->header.user_index;
	parse_data->units_size = data_units_size;
	for (i = 0; i < data_units_size; i++) {
		dw_index = i >> 3;
		offset = i & 7;
		if (!offset)
			parse_data->values[dw_index] = 0;
		parse_data->values[dw_index] |= (uint32_t)data_units[i] << (offset * 4);
	}

	return GKT_SUCCESS;
}

#endif

